package com.zl.redisscheduler.core;

import com.zl.redisscheduler.RedisDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.List;

/**
 *
 *
 * @date 2021/4/8 9:24
 * @author Lizhong
 */
public class RedisOp {
    private RedisDriver redisDriver;
    private boolean isCluster;
    String map = "_MAP";
    String zset = "_ZSET";

    private static final Logger log = LoggerFactory.getLogger(RedisOp.class);
    public RedisOp(RedisDriver redisDriver) {
        this.redisDriver = redisDriver;
        redisDriver.fetch(commands -> {
            try {
                Object isClusterRet = commands.eval("return redis.call('cluster','slots');", Collections.emptyList(), Collections.emptyList());
                if (isClusterRet instanceof List) {
                    log.info("Redis server is cluster mode");
                    isCluster = true;
                }
            } catch (Exception e) {
            }
            return null;
        });
    }

    public void put(String schedulerName, String taskId, long delayTime, long duration) {
        redisDriver.fetch((commands -> {
            String script = null;
            if (duration > 0) {
                script = addTaskScript(schedulerName, taskId, delayTime, duration);
            } else {
                script = addTaskExecOnceScript(schedulerName, taskId, delayTime);
            }
            commands.eval(script, Collections.emptyList(), Collections.emptyList());
            return null;
        }));
    }

    public Object getFirst(String schedulerName) {
        return redisDriver.fetch((commands -> commands.eval(getTaskScript(schedulerName), Collections.emptyList(), Collections.emptyList())));
    }

    public void del(String schedulerName, String taskId) {
        redisDriver.fetch((commands -> {
            commands.eval(delByTaskIdScript(schedulerName, taskId), Collections.emptyList(), Collections.emptyList());
            return null;
        }));
    }

    public void delAll(String schedulerName) {
        redisDriver.fetch((commands -> {
            commands.eval(delAllScript(schedulerName), Collections.emptyList(), Collections.emptyList());
            return null;
        }));
    }

    public int slot(String key) {
        if (key == null) {
            return 0;
        }
        return key.hashCode() % (16384 - 1);
    }

    public String slotInfo(String preFix, String suffix) {
        if (!isCluster) {
            return preFix + suffix;
        }
        int slot = slot(preFix);
        return "{" + slot + "}" + preFix + suffix;
    }

    private String getTaskScript(String schedulerName) {
        String taskMap = slotInfo(schedulerName, map);
        String taskZSet = slotInfo(schedulerName, zset);
        String s = "" +
                "local currentTime=redis.call('TIME')[1];" +
                "local taskList=redis.call('ZRANGEBYSCORE','" + taskZSet + "',0,currentTime );" +
                "for i=1,#taskList do" +
                "   local duration=redis.call('HGET','" + taskMap + "', taskList[i] );" +
                "   if (duration) then" +
                "       redis.call('ZADD', '" + taskZSet + "',currentTime+duration, taskList[i] );" +
                "   else " +
                "       redis.call('ZREM','" + taskZSet + "',  taskList[i] );" +
                "   end;" +
                "end;" +
                "local nextTime=redis.call('ZRANGEBYSCORE','" + taskZSet + "',0,'+inf' ,'Withscores','limit',0,1 )[2];" +
                "table.insert(taskList, 'NEXT_TASK_DURATION_'..(nextTime-currentTime) )" +
                "return taskList;";
        return s;
    }

    private String addTaskScript(String schedulerName, String taskId, long delayTime, long duration) {
        String taskMap = slotInfo(schedulerName, map);
        String taskZSet = slotInfo(schedulerName, zset);
        long first = (delayTime + duration);
        String s = "" +
                "local currentTime=redis.call('TIME')[1];" +
                "redis.call('HSET','" + taskMap + "', '" + taskId + "'," + duration + " );" +
                "redis.call('ZADD', '" + taskZSet + "',currentTime+ tonumber('" + first + "'), '" + taskId + "');";
        return s;
    }

    private String addTaskExecOnceScript(String schedulerName, String taskId, long duration) {
        String taskMap = slotInfo(schedulerName, map);
        String taskZSet = slotInfo(schedulerName, zset);
        String s = "" +
                "local currentTime=redis.call('TIME')[1];" +
                "redis.call('ZADD', '" + taskZSet + "',currentTime+tonumber('" + duration + "'),'" + taskId + "');";
        return s;
    }

    private String delAllScript(String schedulerName) {
        String taskMap = slotInfo(schedulerName, map);
        String taskZSet = slotInfo(schedulerName, zset);
        String s = "" +
                "redis.call('DEL','" + taskZSet + "', '" + taskMap + "' );";
        return s;
    }

    private String delByTaskIdScript(String schedulerName, String taskId) {
        String taskMap = slotInfo(schedulerName, map);
        String taskZSet = slotInfo(schedulerName, zset);
        String s = "" +
                "redis.call('ZREM','" + taskZSet + "', '" + taskId + "' );" +
                "redis.call('HDEL', '" + taskMap + "','" + taskId + "');";
        return s;
    }

}
